## 实验三十六 综合实验

**![62-综合](media/c92bfcbd1ecd7fe91198066d0c9a4df6.jpeg)**

### 🌟 项目简介  
前面我们已经完成了多个独立小实验（如LED闪烁、红外遥控、超声波测距等），每次都要单独上传代码，操作比较麻烦。那能不能把它们“打包”在一起，用一个按键就能切换不同功能呢？  
**当然可以！** 本实验就是这样一个“多功能集成器”：  
✅ 按一次键 → 切换一个功能  
✅ 循环切换 → 6812彩灯 / 红外接收 / 摇杆读取 / 电位器调光 / 超声波测距  
✅ 所有功能共用同一块Pico板和同一份代码，无需反复烧录！

---

### 🔧 工作原理  
我们利用**按键中断**来统计按下次数，再用 `keys % 5` 计算余数（0、1、2、3、4），每个余数对应一个实验功能：  
- `余数 0` → 随机颜色循环点亮 4 颗 6812 RGB 灯  
- `余数 1` → 等待红外遥控信号，并在串口打印按键编码（如“Up”“OK”）  
- `余数 2` → 实时读取摇杆的 X/Y 轴模拟值 + 按键 Z 值，并打印到串口  
- `余数 3` → 旋转电位器调节白色LED亮度（PWM控制）  
- `余数 4` → 触发超声波模块，测量前方距离（单位：厘米）  

所有传感器/模块共用 Pico 的 GPIO 引脚，通过程序逻辑分时控制，互不干扰。

---

### 📦 所需材料  

| ![rpi_pico-smd_1_breadboard](media/c4bec8956b5785f9b6c98b310e182d18.png) | ![KS3017 pico扩展板](media/b9233c67c93c243214388d668afe2eab.png)     | ![KE4001](media/c7e28ad5b3962481026bbc2134e0e90d.png)                | ![KE4012](media/1a90cb6e20f8bdab71de591c68c14a26.png) | ![KE4030](media/0c95f5206cdfa7ac05275fe95fe9ee13.png) |
|--------------------------------------------------------------------------|----------------------------------------------------------------------|----------------------------------------------------------------------|-------------------------------------------------------|-------------------------------------------------------|
| Raspberry Pi Pico板 ×1                                                   | Raspberry Pi Pico扩展板 ×1                                           | keyes DIY电子积木 白色LED模块 ×1                                     | keyes DIY电子积木 单路按键模块 ×1                     | keyes DIY电子积木 旋转电位器模块 ×1                   |
| ![KE4036](media/dc37c6b6608e8f4435d46a8ad2985f9f.png)                    | ![KE4050](media/3fec511423067109a59d269ddb95b9ad.png)                | ![](media/cc99237e6c3651ddc858f2af911b1a1c.png)                      | ![KE4009](media/418bf1fc75c9d7d711605751775e4e45.png) | ![USB线](media/edbfec59fe015bd9987e4b4d542b466d.png)  |
| keyes DIY电子积木 红外接收模块 ×1                                        | keyes DIY电子积木 摇杆模块 ×1                                        | keyes brick HC-SR04超声波传感器 ×1                                   | Keyes DIY电子积木 6812 RGB模块 ×1                     | MicroUSB线 ×1                                         |
| ![QQ图片20220426165815](media/47dfea4b451d23ee80c7faaf96e1ab6f.jpeg)     | ![QQ图片20220426165719](media/178ac5a6046142ac33d08d63ead5c7ad.jpeg) | ![QQ图片20220426165725](media/68c063c06bfbd73d3ab4b6b2bfcc5c3a.jpeg) | ![](media/9565387ea4d3df1816ef34e5aeaad3db.png)       |                                                       |
| 防反插3Pin ×5                                                            | 防反插4Pin ×1                                                        | 防反插5Pin ×1                                                        | 遥控器 ×1                                             |                                                       |

> 💡 小提示：所有模块都使用标准防反插接口，插错方向无法插入，接线更安全、更省心！

---

### 🔌 接线图  

**![24](media/eb2376b414324494a05f3bf7ff2780b5.png)**

📌 **关键引脚对照表（请务必按此连接）：**  
| 模块名称         | 连接引脚（Pico端） | 说明                     |
|------------------|--------------------|--------------------------|
| 白色LED模块      | GP14               | PWM输出，控制亮度        |
| 单路按键模块     | GP16               | 下降沿触发中断           |
| 旋转电位器模块   | ADC28（GP28）      | 模拟输入，读取旋钮位置   |
| 红外接收模块     | GP11               | 数字输入，接收红外信号   |
| 摇杆模块（Z键）  | GP22               | 数字输入，检测是否按下   |
| 摇杆模块（X轴）  | ADC26（GP26）      | 模拟输入，X方向电压      |
| 摇杆模块（Y轴）  | ADC27（GP27）      | 模拟输入，Y方向电压      |
| 超声波模块（Trig）| GP6                | 输出触发脉冲             |
| 超声波模块（Echo）| GP7                | 输入回波信号             |
| 6812 RGB模块     | GP15               | PIO高速驱动，单线协议    |

> ✅ 接线完成后，请再次核对——尤其注意 **GP15（RGB）、GP11（红外）、GP6/GP7（超声波）** 这几组不能接错！

---

### 💻 示例代码（MicroPython）

```python
# Keyes Starter Kit for Raspberry Pi Pico
# 课程 36
# Comprehensive experiment

from machine import Pin, PWM, ADC
import array, time
import random
import rp2

# 初始化各传感器
potentiometer = ADC(28)           # 电位器 → GP28
button = Pin(16, Pin.IN, Pin.PULL_UP)  # 按键 → GP16（上拉，按下为低电平）
led = PWM(Pin(14))                # 白色LED → GP14
led.freq(1000)

ird = Pin(11, Pin.IN)             # 红外接收 → GP11
B = Pin(22, Pin.IN, Pin.PULL_UP) # 摇杆Z键 → GP22（上拉）
X = ADC(26)                       # 摇杆X轴 → GP26
Y = ADC(27)                       # 摇杆Y轴 → GP27

# 超声波模块
trigger = Pin(6, Pin.OUT)
echo = Pin(7, Pin.IN)

# 6812 RGB灯配置
NUM_LEDS = 4
PIN_NUM = 15
brightness = 0.2

# 红外遥控指令码库（Keyes遥控器常用按键）
act = {
    "1": "LLLLLLLLHHHHHHHHLHHLHLLLHLLHLHHH",
    "2": "LLLLLLLLHHHHHHHHHLLHHLLLLHHLLHHH",
    "3": "LLLLLLLLHHHHHHHHHLHHLLLLLHLLHHHH",
    "4": "LLLLLLLLHHHHHHHHLLHHLLLLHHLLHHHH",
    "5": "LLLLLLLLHHHHHHHHLLLHHLLLHHHLLHHH",
    "6": "LLLLLLLLHHHHHHHHLHHHHLHLHLLLLHLH",
    "7": "LLLLLLLLHHHHHHHHLLLHLLLLHHHLHHHH",
    "8": "LLLLLLLLHHHHHHHHLLHHHLLLHHLLLHHH",
    "9": "LLLLLLLLHHHHHHHHLHLHHLHLHLHLLHLH",
    "0": "LLLLLLLLHHHHHHHHLHLLHLHLHLHHLHLH",
    "Up": "LLLLLLLLHHHHHHHHLHHLLLHLHLLHHHLH",
    "Down": "LLLLLLLLHHHHHHHHHLHLHLLLLHLHLHHH",
    "Left": "LLLLLLLLHHHHHHHHLLHLLLHLHHLHHHLH",
    "Right": "LLLLLLLLHHHHHHHHHHLLLLHLLLHHHHLH",
    "Ok": "LLLLLLLLHHHHHHHHLLLLLLHLHHHHHHLH",
    "*": "LLLLLLLLHHHHHHHHLHLLLLHLHLHHHHLH",
    "#": "LLLLLLLLHHHHHHHHLHLHLLHLHLHLHHLH"
}

#红外解码函数（读取一次完整信号）
def read_ircode(ird):
    wait = 1
    complete = 0
    seq0 = []
    seq1 = []
    
    while wait == 1:
        if ird.value() == 0:
            wait = 0
    
    while wait == 0 and complete == 0:
        start = time.ticks_us()
        while ird.value() == 0:
            ms1 = time.ticks_us()
            diff = time.ticks_diff(ms1, start)
            seq0.append(diff)
        
        while ird.value() == 1 and complete == 0:
            ms2 = time.ticks_us()
            diff = time.ticks_diff(ms2, ms1)
            if diff > 10000:
                complete = 1
            seq1.append(diff)
    
    # 解析高/低电平时间，生成"H"/"L"字符串
    code = ""
    for val in seq1:
        if val < 2000:
            if val < 700:
                code += "L"
            else:
                code += "H"
    
    # 匹配预设指令
    command = ""
    for k, v in act.items():
        if code == v:
            command = k
            break
    return command if command else "Unknown"

#PIO程序：驱动6812 RGB灯（底层高速时序）
@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=True, pull_thresh=24)
def sk6812():
    T1 = 2
    T2 = 5
    T3 = 3
    wrap_target()
    label("bitloop")
    out(x, 1) .side(0) [T3 - 1]
    jmp(not_x, "do_zero") .side(1) [T1 - 1]
    jmp("bitloop") .side(1) [T2 - 1]
    label("do_zero")
    nop() .side(0) [T2 - 1]
    wrap()

# 初始化PIO状态机（驱动GP15）
sm = rp2.StateMachine(0, sk6812, freq=8_000_000, sideset_base=Pin(PIN_NUM))
sm.active(1)

# RGB灯显示缓冲区
ar = array.array("I", [0 for _ in range(NUM_LEDS)])

def pixels_show():
    dimmer_ar = array.array("I", [0 for _ in range(NUM_LEDS)])
    for i, c in enumerate(ar):
        r = int(((c >> 8) & 0xFF) * brightness)
        g = int(((c >> 16) & 0xFF) * brightness)
        b = int((c & 0xFF) * brightness)
        dimmer_ar[i] = (g << 16) + (r << 8) + b
    sm.put(dimmer_ar, 8)
    time.sleep_ms(10)

def pixels_set(i, color):
    ar[i] = (color[1] << 16) + (color[0] << 8) + color[2]

#超声波测距函数（单位：厘米）
def getDistance(trigger, echo):
    trigger.low()
    time.sleep_us(2)
    trigger.high()
    time.sleep_us(10)
    trigger.low()
    
    while echo.value() == 0:
        start = time.ticks_us()
    while echo.value() == 1:
        end = time.ticks_us()
    
    d = (end - start) * 0.0343 / 2  # 声速0.0343 cm/us，除以2得单程距离
    return d

# 全局变量：记录按键次数
keys = 0

#按键中断回调函数（每按一次，keys+1）
def toggle_handle(pin):
    global keys
    time.sleep_ms(20)  # 消抖
    if pin.value() == 0:  # 确保是按下动作（低电平）
        keys += 1

# 绑定中断（下降沿触发）
button.irq(trigger=Pin.IRQ_FALLING, handler=toggle_handle)

#各功能函数
def show6812():
    R = random.randint(0, 255)
    G = random.randint(0, 255)
    B = random.randint(0, 255)
    for i in range(NUM_LEDS):
        pixels_set(i, (R, G, B))
    pixels_show()
    time.sleep(0.3)

def IRreceive():
    command = read_ircode(ird)
    print("IR Command:", command)

def showJoystick():
    B_value = B.value()
    X_value = X.read_u16()
    Y_value = Y.read_u16()
    print(f"Joystick → Button:{B_value}, X:{X_value}, Y:{Y_value}")
    time.sleep(0.1)

def adjustLight():
    pot_value = potentiometer.read_u16()
    led.duty_u16(pot_value)  # 0~65535 映射亮度
    print(f"Potentiometer: {pot_value}")

def showDistance():
    try:
        distance = getDistance(trigger, echo)
        print(f"Distance: {distance:.2f} cm")
    except:
        print("Distance: Error")
    time.sleep(0.1)

#主循环：根据按键次数切换功能
while True:
    nums = keys % 5
    print(f"Mode: {nums}")
    
    if nums == 0:
        show6812()
    elif nums == 1:
        IRreceive()
    elif nums == 2:
        showJoystick()
    elif nums == 3:
        adjustLight()
    elif nums == 4:
        showDistance()
```

---

### 📚 代码解析（小学生也能懂！）

| 代码片段 | 是什么意思？ | 小贴士 |
|----------|--------------|--------|
| `button.irq(...)` | 告诉Pico：“只要按键一按下去，立刻执行 `toggle_handle` 函数！” | 中断就像“电话铃响”，不用一直盯着看，来了就处理 |
| `keys % 5` | “用按键次数除以5，只看余数是多少” → 0→1→2→3→4→0→1…永远循环 | `%` 叫“取余运算符”，是循环切换的核心！ |
| `pixels_set(i, (R,G,B))` | 给第 `i` 颗RGB灯设置红(R)、绿(G)、蓝(B)的亮度（0~255） | 三原色混合，就能变出任何颜色！🌈 |
| `led.duty_u16(pot_value)` | 把电位器读到的数字（0~65535），直接变成LED的亮度 | 数字越大，灯越亮；0就是完全熄灭 |
| `getDistance(...)` | 发个“滴”声波出去，听它弹回来用了多久，算出有多远 | 就像蝙蝠用回声定位！🦇 |

---

### ✅ 实验现象（按顺序观察）

**![12](media/b116ddc11e0c10587b9de5f043298603.jpeg)**  
接好线，用USB线给Pico通电，打开Thonny或串口工具（波特率115200），看到如下效果：

1. **初始状态（按键0次 → 余数0）**  
   → 四颗6812灯珠开始随机变色，每0.3秒换一次颜色！  
   ![](media/8827a522c5cb756c871f0ddf55bca52f.png)

2. **按第1下 → 余数1**  
   → 彩灯停止，串口等待红外信号。拿起遥控器对准接收头，按任意键 → 显示 `"IR Command: Up"` 或 `"Ok"` 等。  
   ![1631674182(1)](media/6b7eeb8aa4f566d27020dcf9060feb65.png)

3. **按第2下 → 余数2**  
   → 串口开始快速打印摇杆数据，例如：`Joystick → Button:1, X:32768, Y:65535`（Z没按是1，X居中约32768）  
   ![](media/8c3b088e435f1f783e3e7ba390e884b0.png)

4. **按第3下 → 余数3**  
   → 白色LED亮度随电位器旋转实时变化，串口同步显示数值（0最暗，65535最亮）  
   ![](media/e9edca1843047733a06a7743f924aa2b.png)

5. **按第4下 → 余数4**  
   → 串口每0.1秒打印一次距离，如 `Distance: 12.45 cm`（手靠近/远离会变化）  
   ![](media/6885701019cc138d5ac94c9f04b3b085.png)

6. **按第5下 → 余数0**  
   → 回到第一步，彩灯再次闪烁！从此无限循环 ✅  

---

### ⚠️ 注意事项（安全又成功的关键！）

- 🔌 **务必使用防反插杜邦线**，插错方向可能烧毁模块！  
- 🔋 **Pico仅靠USB供电即可**，不要额外接电源，避免电压冲突。  
- 📡 **红外接收头要正对遥控器发射头**（黑色小圆点），距离建议10~30cm。  
- 📏 **超声波模块前方保持空旷**，避免斜面/吸音材料（如毛衣、海绵）影响测距。  
- 🐞 **如果串口无反应**：检查Thonny是否选对端口、波特率是否为115200、USB线是否支持数据传输（有些充电线不行）。  
- 🔄 **按键失灵？** 多按几次，或重启Pico（拔插USB），可能是接触不良或消抖未生效。

---

### 🧠 扩展思维  
在本课 6812 彩灯随机闪烁的基础上，如果想让它实现「呼吸灯」效果（渐亮→渐暗→循环），该在 `show6812()` 函数中怎样修改？

![](media/dba9c7bf22beaa92aae5a8aad80fe88f.png)